import EngineContext from "./EngineContext"
import moduleEnum from "./enum/module.enum"
import Module from "./module/Module"
import { itemData } from "./ModuleLoader"
import Logger from "./utils/Logger"
import OpenDeferred from "./utils/OpenDeferred"
const s = new Logger("module-loader")
export default class SymbolLoader {
  symbolMap: Map<symbol, itemData>
  constructor() {
    this.symbolMap = new Map()
  }
  registerModule(e: symbol, t: (name: string) => Promise<typeof Module>) {
    if (this.symbolMap.has(e)) throw Error(`Type ${String(e)} already registered`)
    this.symbolMap.set(e, {
      es6ModulePromise: t,
      ctorPromise: new OpenDeferred(),
      initPromise: new OpenDeferred(),
      state: moduleEnum.Registered
    })
  }
  moduleRegistered(e: symbol) {
    return this.symbolMap.has(e)
  }
  moduleLoaded(e: symbol) {
    const t = this.symbolMap.get(e)
    return !!t && ![moduleEnum.Registered, moduleEnum.Unloaded].includes(t.state)
  }
  getModule<T extends Module = Module>(e: symbol): Promise<T> {
    const t = this.symbolMap.get(e)
    if (!t) throw Error(`Trying to get ${String(e)} before it was registered!`)
    if (t.state === moduleEnum.Unloaded) throw Error(`${String(e)} module unloaded`)
    return t.initPromise.nativePromise()
  }
  loadModule<A extends typeof Module>(e: EngineContext, t: symbol, config = {}) {
    const n: itemData<A["prototype"], A> = this.symbolMap.get(t)
    if (!n) throw Error()
    if (n.state === moduleEnum.Initialized) return n
    if (n.state !== moduleEnum.Registered && n.state !== moduleEnum.Unloaded) throw Error(`${String(t)} already loading`)
    n.state = moduleEnum.Loading
    const o = String(t)
    n.es6ModulePromise(o)
      .then(o => {
        const a = new o()
        n.ctorPromise.resolve(a).then(() => {
          const t = Date.now()
          let o = !1
          setTimeout(() => {
            o || s.debug(`${a.name} init took over 2 seconds`)
          }, 2000)
          a.init(config, e)
            .then(() => {
              s.debug(`${a.name} init took ${(Date.now() - t) / 1000} secs.`)
              o = !0
              n.state = moduleEnum.Initialized
              n.module = a
              n.initPromise.resolve(a)
            })
            .catch(e => {
              n.initPromise.reject(e)
            })
        })
      })
      .catch(e => {
        s.error(`Failed to load es6Module for ${String(t)}: ${e}`), n.initPromise.reject(e)
      })
    return n
  }
  whenQueueEmpties() {
    const e: any[] = []
    for (const [s, t] of this.symbolMap) t.state === moduleEnum.Loading && e.push(t.initPromise)
    return OpenDeferred.all(e).nativePromise()
  }
  unloadModule(e: symbol) {
    const t = this.symbolMap.get(e)
    if (!t) throw Error("Attempting to unload unregistered module")
    if (t.state === moduleEnum.Unloaded || t.state === moduleEnum.Registered) throw Error("Attempting to unload not-loaded module")
    ;(t.state = moduleEnum.Unloaded), (t.ctorPromise = new OpenDeferred()), (t.initPromise = new OpenDeferred()), (t.module = void 0)
  }
}
